/*
* Copyright (c) 2010 Michael Collins
*
* This file is part of TerraFirma.
*
* TerraFirma is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TerraFirma is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with TerraFirma.  If not, see <http://www.gnu.org/licenses/>.
*/

//DEDICATED SERVER IMPLEMENTATION ONLY!!
#ifdef SERVER
#include "logic_thread.h"
#include "timer.h"
#include <boost/date_time/posix_time/posix_time.hpp>
#include "context.h"
#include "lua/entity_factory.hpp"

namespace engine
{
    static shared_entity_factory Shared_Entity_Factory;

    logic_thread::logic_thread() : task_thread<Message_Logic>(name())
    {
        //ctor
        running = true;

        L = lua_open();

        luaL_openlibs(L); //Load lua libraries
        process_lua_file("./lua/base.lua");
        process_lua_file("./lua/entity.lua");
    }

    logic_thread::~logic_thread()
    {
        lua_close(L);
    }

    int logic_thread::process_lua_file(const char* file)
    {
        printf("Running lua file %s...\n", file);
        int status = luaL_dofile(L, file);

        if (status)  //Error condition
        {
            //Print the error message sitting on the top of the stack
            fprintf(stderr, "%s\n", lua_tostring(L, -1));
            lua_pop(L, 1); //Pop the error message from the stack
        }
        return status;
    }

    /**
     * Function that sleeps the thread until the next tick occurs, or until a message comes in on the queue
     * in which case it returns true, otherwise false
     */
    bool logic_thread::tick_wait()
    {
        //Make sure this runs very regularly at every game tick somehow
        static timer tick_clock;

        boost::mutex dummy; //This is probably really stupid of me. Nobody really needs this mutex, we just want to be able to wake a condvar
        boost::unique_lock<boost::mutex> lock(dummy);

        time curr_time; //Time in milliseconds since the last tick
        while (msg_queue.isempty() && ((curr_time = tick_clock.elapsed()) < TICK_PERIOD))
        {
            unsigned int remaining_ms = (TICK_PERIOD - curr_time);
            msg_queue.read.timed_wait (lock, boost::posix_time::milliseconds(remaining_ms));
        }
        //In the case that the timer expired, we need to reset the timer immediately so regularity is sustained!
        if (curr_time >= TICK_PERIOD)
            tick_clock.reset();
        return !msg_queue.isempty();
    }

    /**
     * Function that sleeps the thread t seconds, or until a message comes in on the queue
     * of command_type type, in which case it returns true, otherwise false
     */
    bool logic_thread::timed_wait(unsigned int t, uint8_t command_type)
    {
        //Make sure this runs very regularly at every game tick somehow
        timer clock;

        boost::mutex dummy; //This is probably really stupid of me. Nobody really needs this mutex, we just want to be able to wake a condvar
        boost::unique_lock<boost::mutex> lock(dummy);

        unsigned int curr_time; //Time in seconds since the last tick
        while ((curr_time = clock.get_elapsed_seconds()) < t)
        {
            unsigned int remaining_s = t - curr_time;
            msg_queue.read.timed_wait (lock, boost::posix_time::seconds(remaining_s));
            if (!msg_queue.isempty())
            {
                //We got a message in the pipeline. Make sure that it matches the command type
                if (msg_queue.front().header().get_type() == command_type)
                    return true;
            }
        }
        return false;
    }

    void logic_thread::handle_request_event (const Request_Join_Message& msg)
    {
        //static hostid connecting_player = 0;
        //this is a SERVER

        //We have a new connected host wishing to join
        //Create a player object
        unsigned int player_id;
        game::Player& new_player = context::Instance().world.players.add(id);
        new_player.host_id = msg.header.get_src_host();
        new_player.id = player_id;
        strncpy(new_player.name, msg.desired_name, 63);
        new_player.name[63] = '\0';
        new_player.team = msg.desired_team;
        new_player.score = 0;
        //Create a ghost vehicle at the origin for the new player to fly around in
        new_player.alive = false;
        new_player.ready = false;
        new_player.vehicle = Shared_Entity_Factory.Create(L, "ghost", fixedpoint::vec2d(), fixedpoint::vec2d());
        //context::Instance().world.players.add(new_player);

        //Accept the join request and include the player's id
        Accept_Join_Message reply(new_player.id);
        MPI.enqueue_remote(msg.header.get_src_host(), &reply.header); //Send the replyl;
        //Now signal to all the clients to add the new player to the clientside player lists
        Player_Connect_Message conn(new_player);
        MPI.enqueue_all_other_hosts(new_player.host_id, &conn.header);
        //Send the game state (players and all the entities and shit) to the new player to bring him up to speed
        serialize_gamestate(msg.header.get_src_host());

        //Call the lua player_add handler
    }

    void logic_thread::process_message(const Message_Logic& msg)
    {
        //Process the incoming message from another host or thread
        if (msg.header().major_type() == BCAST_CLOSE)
            running = false;
        switch (msg.header().minor_type())
        {
        case REQUEST_JOIN:
            handle_request_event(msg.cast<Request_Join_Message>());
            break;
        case PLAYER_UPDATE:
            //Update our records and broadcast change to all hosts
            break;
        case PLAYER_REMOVE:
            //Player just left. Remove records and make sure clients do the same
            handle_player_remove(msg.cast<Player_Disconnect_Message>());
            break;
        case CONTROL_EVENT:
            /*printf("Control message. Control id = %d, state = %x\n",
                   msg.command_payload.player_control.control_id,
                   msg.command_payload.player_control.state);*/
            printf("Control message received from host %d\n",
                   msg.header().get_src_host());
            break;
        }
    }

    void logic_thread::handle_player_remove(const Player_Disconnect_Message& msg)
    {
        context::Instance().world.players.remove(msg.player);
    }

    void logic_thread::handle_control_event(const Player_Control_Message& msg)
    {
        //The player is always the local player, so we don't need to resolve that
        context& cntxt(context::Instance());
        //Determine the player's vehicle, and push it onto the stack
        game::Entity& vehicle(cntxt.world.entities[cntxt.world.players[cntxt.playerid].vehicle]);
        lua_getglobal(L, "shared_entities"); //1
        lua_pushinteger(L, (lua_Integer) vehicle.id); //2
        lua_gettable(L, -2);//2
        lua_getfield(L, -1, "control"); //3
        lua_pushvalue(L, -2); //self //4
        lua_pushinteger(L, (lua_Integer) msg.control_id); //Control id //5
        lua_pushinteger(L, (lua_Integer) msg.type);//control type (bool, 1d, 2d) //6
        //Create a table that represents the control state (it's polymorphic so a table will work best)
        if(msg.type == Player_Control_Message::ANALOG_2D)
        {
            lua_createtable(L, 0, 2);//7
            lua_pushstring(L, "x");
            lua_pushinteger(L, (lua_Integer) msg.state.analog.x);
            lua_settable(L, -3);
            lua_pushstring(L, "y");
            lua_pushinteger(L, (lua_Integer) msg.state.analog.y);
            lua_settable(L, -3);
        } else
            lua_pushboolean(L, msg.state.binary); //7

        //Call the lua function "control"
        lua_pcall(L, 4, 0, 0); //2
        lua_pop(L, 2);
    }

    void logic_thread::F(const boost::thread::id tid)
    {
        while (running)
        {
            //Wait for a tick or a message to occur
            //and handle pending messages first
            if (tick_wait())
            {
                do
                {
                    process_message(msg_queue.front());
                    msg_queue.pop();
                }
                while (!msg_queue.isempty());
            }
            else
            {
                //No messages came in during the tick period. Do the idle routine
                idle();
            }
        }
    }

    void logic_thread::serialize_gamestate(hostid host)
    {
        //Server updates the specified host with the data to reconstruct the world
        //First, add all the hosts
        game::World& w = context::Instance().world;
        segmented_pool<game::Player>::const_iterator it;
        for (it = w.players.begin(); it < w.players.end(); it++)
        {
            Player_Connect_Message msg(*it);
            MPI.enqueue_remote(host, &msg.header);
        }

        //Serialize Entities

        //Serialize clientside lua state
    }

    void logic_thread::idle()
    {

    }
}
#endif //SERVER
